﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace Xceed.Silverlight.ListBox
{
  internal class RemovingItemTransitionAnimation : TransitionAnimation
  {
    internal RemovingItemTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetRemovingItemDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseOut;
      opacityEasing.Power = 6;

      PowerEase itemsRemovedEasing = new PowerEase();
      itemsRemovedEasing.EasingMode = EasingMode.EaseOut;
      itemsRemovedEasing.Power = 6;

      PowerEase otherItemsEasing = new PowerEase();
      otherItemsEasing.EasingMode = EasingMode.EaseInOut;
      otherItemsEasing.Power = 6;

      m_opacityEasing = opacityEasing;
      m_removedItemsMovementEasing = itemsRemovedEasing;
      m_otherItemsMovementEasing = otherItemsEasing;

    }

    internal double RemovePositionX
    {
      get;
      private set;
    }

    internal double RemovePositionY
    {
      get;
      private set;
    }

    internal double RemoveWidth
    {
      get;
      private set;
    }

    internal double RemoveHeight
    {
      get;
      private set;
    }

    internal Orientation Orientation
    {
      get;
      set;
    }

    internal override void AddContainer( ListBoxContainer container )
    {
      //Nothing to do
    }

    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> changedElements )
    {
      //For this transition animation, I need to assess the position ("pixels") where the remove is performed.

      //I also need to know total items travel to impact added containers correctly.

      double lowestRemovalX = double.MaxValue;
      double lowestRemovalY = double.MaxValue;

      double highestRemovalX = double.MinValue;
      double highestRemovalY = double.MinValue;

      IEnumerable<ListBoxContainer> removedElements = from kvp in changedElements
                                                     let layoutChangeInfo = kvp.Value
                                                     let removedContainer = kvp.Key
                                                     where layoutChangeInfo.ChangeType == ChangeType.ElementRemoved
                                                     select removedContainer;

      bool hadRemoval = false;

      foreach( ListBoxContainer container in removedElements )
      {
        Point containerPosition = container.ActualComputedPosition;

        double elementHeight = container.ActualElementHeight;
        double elementWidth = container.AbsoluteElementWidth;

        if( containerPosition.X < lowestRemovalX )
        {
          lowestRemovalX = containerPosition.X;
        }

        if( containerPosition.Y < lowestRemovalY )
        {
          lowestRemovalY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestRemovalX )
        {
          highestRemovalX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestRemovalY )
        {
          highestRemovalY = containerPosition.Y + elementHeight;
        }

        hadRemoval = true;
      }


      if( hadRemoval )
      {
        this.RemovePositionX = lowestRemovalX;
        this.RemovePositionY = lowestRemovalY;
        this.RemoveWidth = highestRemovalX - lowestRemovalX;
        this.RemoveHeight = highestRemovalY - lowestRemovalY;
      }
    }

    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      switch( changeInfo.ChangeType )
      {
        case ChangeType.ElementAdded:
          this.PrepareAddedElement( container, changeInfo );
          break;

        case ChangeType.ElementRemoved:
          this.PrepareRemovedElement( container, changeInfo );
          break;

        //If element is changed, don't change a thing.
        case ChangeType.ElementChanged:
          this.PrepareChangedElement( container, changeInfo );
          break;
        default:
          break;
      }

      //systematically return true. All elements flagged as changed are handled in this transition.
      return true;
    }

    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {

      if( container == null )
        throw new ListBoxInternalException();


      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();

    }

    private void PrepareAddedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an Remove action, added elements are those brought into the layout because some content is removed from the layout.
      //In that case, this class will animate them from the height of removal to their respective position

      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      PositionAnimation positionAnimation = new PositionAnimation();

      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.ToReferencePosition;
      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;

      if( this.Orientation == Orientation.Vertical )
      {
        positionAnimation.XChange = double.NaN;
        positionAnimation.YChange = this.RemoveHeight;

        sizeAnimation.MaximumHeightChange = -this.RemoveHeight;
      }
      else
      {
        positionAnimation.XChange = this.RemoveWidth;
        positionAnimation.YChange = double.NaN;

        sizeAnimation.MaximumWidthChange = -this.RemoveWidth;
      }

      sizeAnimation.WidthChange = double.NaN;
      sizeAnimation.HeightChange = double.NaN;

      opacityAnimation.OpacityChange = -changeInfo.OpacityChange;

      container.AddAnimation( sizeAnimation );
      container.AddAnimation( opacityAnimation );
      container.AddAnimation( positionAnimation );

    }

    private void PrepareChangedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an Remove action, added elements are those brought into the layout because some content is removed from the layout.
      //In that case, this class will animate them from the height of removal to their respective position
      PositionAnimation positionAnimation = new PositionAnimation();
      SizeAnimation sizeAnimation = new SizeAnimation();
      RotateAnimation rotationAnimation = new RotateAnimation();
      ScaleAnimation scaleAnimation = new ScaleAnimation();

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;
      ( ( IAnimation )rotationAnimation ).ParentTransition = this;
      ( ( IAnimation )rotationAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )scaleAnimation ).ParentTransition = this;
      ( ( IAnimation )scaleAnimation ).EasingFunction = m_otherItemsMovementEasing;

      positionAnimation.XChange = changeInfo.XChange;
      positionAnimation.YChange = changeInfo.YChange;
      rotationAnimation.AngleChange = changeInfo.AngleChange;
      if( this.Orientation == Orientation.Vertical )
      {
        sizeAnimation.MaximumHeightChange = -this.RemoveHeight;
      }
      else
      {
        sizeAnimation.MaximumWidthChange = -this.RemoveWidth;
      }

      sizeAnimation.WidthChange = double.NaN;
      sizeAnimation.HeightChange = double.NaN;
      scaleAnimation.ScaleChange = changeInfo.ScaleChange;

      container.AddAnimation( rotationAnimation );
      container.AddAnimation( positionAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( scaleAnimation );
    }

    private void PrepareRemovedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a Remove are items that are shifted out of view ( they are removed )

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //Animation for these items is to push them away by the amount of inserted items.
      Point elementActualPosition = container.ActualComputedPosition;
      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      RotateAnimation rotateAnimation = new RotateAnimation();

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_removedItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;
      ( ( IAnimation )rotateAnimation ).EasingFunction = m_removedItemsMovementEasing;
      ( ( IAnimation )rotateAnimation ).ParentTransition = this;
      rotateAnimation.AngleChange = changeInfo.AngleChange;

      sizeAnimation.WidthChange = double.NaN; // do not change its width
      sizeAnimation.HeightChange = double.NaN; // do not change its height.

      opacityAnimation.OpacityChange = changeInfo.OpacityChange;

      container.AddAnimation( rotateAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( opacityAnimation );

    }

    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_removedItemsMovementEasing;
    private IEasingFunction m_otherItemsMovementEasing;
  }
}
